﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework;
using System.Globalization;

namespace Brain.ModelExporter
{
    public class BrainModelWriter
    {
        public List<MeshData> Meshes;
        public string FileName;
        public ExporterOptions Options;

        public BrainModelWriter()
        {
            Meshes = new List<MeshData>();

            Options = new ExporterOptions();
        }

        public void SetFileName(string fileName)
        {
            this.FileName = fileName;
        }

        public void AddNewMesh()
        {
            MeshData mesh = new MeshData();
            lastMesh = mesh;
            Meshes.Add(mesh);
        }
        private MeshData lastMesh;

        public void SubmitPosition(float X, float Y, float Z)
        {
            if (lastMesh.Positions == null)
            {
                lastMesh.Positions = new List<Vector3>();
            }

            lastMesh.Positions.Add(new Vector3(X, Y, Z));
        }

        public void SubmitNormal(float X, float Y, float Z)
        {
            if (lastMesh.Normals == null)
            {
                lastMesh.Normals = new List<Vector3>();
            }

            lastMesh.Normals.Add(new Vector3(X, Y, Z));
        }

        public void SubmitTexCoord(float X, float Y)
        {
            if (lastMesh.TexCoords == null)
            {
                lastMesh.TexCoords = new List<Vector2>();
            }

            lastMesh.TexCoords.Add(new Vector2(X, Y));
        }

        public void SubmitIndice(int indice)
        {
            lastMesh.Indices.Add(indice);
        }

        public void Save()
        {
            if (!string.IsNullOrEmpty(this.FileName))
            {
                if (File.Exists(this.FileName))
                {
                    File.Delete(this.FileName);
                }

                MakeMiracles();

                using (FileStream stream = new FileStream(this.FileName, FileMode.Create))
                {
                    Save(stream);
                }
            }
        }

        public void MakeMiracles()
        {
            // On this method we will check for anything that
            // might be wrong with our model, and LITERALLY
            // DO MIRACLES to make it work on XNA.
            // We'll do whatever it costs to make it run
            // If there are problems that can be easily fixed by the user, we will notify
            // the user of them.

            // First, we need OPTIMIZE the Mesh,
            // because the mesh can get here with
            // the number of indices equal to
            // the number of texture coordinates,
            // but only 2/9 of vertices positions.
            // This can shit the model, so we have to optimize
            // and find a way to make Vertices Positions equal
            // to Texture Coordinates

            // To do that, we will check every index on the list,
            // and check how much times does the index appears there,
            // so we can now how much triangles share the same vertex.
            // For example, 3DS Max gives a "box" with only 8 vertices,
            // buuut, every vertice is shared by 3 triangles, that have differents
            // normals and Texture Coordinates. Only 8 vertices would destroy
            // the model, make lighting horrifying and everything bad.
            // So, every verts is shared 3 times, so need 3 times more vertices,
            // ending in 24 vertices. BUT, we have to change the Indices
            // to adapt them to the new index ordering...
            // The concept is easy. The hard part is to make it work!

            for (int x = 0; x < this.Meshes.Count; x++)
            {
                MeshData mesh = this.Meshes[x];

                List<Vertex> vertices = new List<Vertex>();
                Dictionary<int, List<UsedIndex>> used = new Dictionary<int, List<UsedIndex>>();
                Dictionary<int, List<UsedIndex>> newUsed = new Dictionary<int, List<UsedIndex>>();

                // We will diferentiate vertices by their normals
                List<Vector3> Normals = null;
                if (mesh.Normals == null)
                {
                    Normals = new List<Vector3>(mesh.Indices.Count / 3);
                }
                for (int i = 0; i < mesh.Indices.Count - 2; i += 3)
                {
                    int index0 = mesh.Indices[i];
                    int index1 = mesh.Indices[i + 1];
                    int index2 = mesh.Indices[i + 2];

                    Vector3 pos0 = mesh.Positions[index0];
                    Vector3 pos1 = mesh.Positions[index1];
                    Vector3 pos2 = mesh.Positions[index2];

                    int tri = i / 3;
                    if (mesh.Normals == null)
                    {
                        Vector3 normal = CalculateNormal(pos0, pos1, pos2);
                        Normals.Add(normal);
                    }

                    Add(used, index0, tri);
                    Add(used, index1, tri);
                    Add(used, index2, tri);
                }
                if (Normals != null)
                {
                    mesh.Normals = Normals;
                }

                // Find repeated Normals, and remove them
                for (int i = 0; i < mesh.Indices.Count; i++)
                {
                    int index = mesh.Indices[i];

                    if (!newUsed.ContainsKey(index))
                    {
                        Vector3 normal;
                        if (mesh.Normals.Count == mesh.Indices.Count)
                        {
                            normal = mesh.Normals[i];
                        }
                        else
                        {
                            normal = mesh.Normals[index];
                        }

                        List<UsedIndex> shared = used[index];
                        List<UsedIndex> newShared = new List<UsedIndex>();

                        List<Vector3> found = new List<Vector3>();
                        for (int z = 0; z < shared.Count; z++)
                        {
                            UsedIndex u = shared[z];

                            Vector3 norm = mesh.Normals[u.TriangleIndex];

                            if (!found.Contains(norm))
                            {
                                found.Add(norm);

                                newShared.Add(u);
                            }
                        }

                        newUsed.Add(index, newShared);
                    }
                }

                // Now that we have a clean list,
                // we will make new indices for the model

                List<int> indices = new List<int>();

                // Loop through all Used List
                foreach (KeyValuePair<int, List<UsedIndex>> pair in newUsed)
                {
                    for (int z = 0; z < pair.Value.Count; z++)
                    {
                        UsedIndex u = pair.Value[z];
                        int tri = u.TriangleIndex * 3;
                        int ind = u.MainIndex;

                        Vector3 pos = mesh.Positions[ind];

                        Vector3 normal;
                        if (mesh.Normals.Count == mesh.Indices.Count)
                        {
                            normal = mesh.Normals[tri];
                        }
                        else
                        {
                            normal = mesh.Normals[u.TriangleIndex];
                        }

                        Vertex vertex = new Vertex();
                        vertex.Normal = normal;
                        vertex.Position = pos;
                        vertices.Add(vertex);
                    }
                }

                for (int i = 0; i < mesh.Indices.Count - 2; i+= 3)
                {
                    int index0 = mesh.Indices[i];
                    int index1 = mesh.Indices[i + 1];
                    int index2 = mesh.Indices[i + 2];

                    Vector3 pos0 = mesh.Positions[index0];
                    Vector3 pos1 = mesh.Positions[index1];
                    Vector3 pos2 = mesh.Positions[index2];

                    int tri = i / 3;
                    Vector3 normal;
                    if (mesh.Normals.Count == mesh.Indices.Count)
                    {
                        normal = mesh.Normals[i];
                    }
                    else
                    {
                        normal = mesh.Normals[tri];
                    }

                    for (int z = 0; z < vertices.Count; z++)
                    {
                        Vertex v = vertices[z];
                        if (v.Normal == normal &&
                            v.Position == pos0)
                        {
                            indices.Add(z);
                            break;
                        }
                    }
                    for (int z = 0; z < vertices.Count; z++)
                    {
                        Vertex v = vertices[z];
                        if (v.Normal == normal &&
                            v.Position == pos1)
                        {
                            indices.Add(z);
                            break;
                        }
                    }
                    for (int z = 0; z < vertices.Count; z++)
                    {
                        Vertex v = vertices[z];
                        if (v.Normal == normal &&
                            v.Position == pos2)
                        {
                            indices.Add(z);
                            break;
                        }
                    }


                }

                int count = 0;
                foreach (KeyValuePair<int, List<UsedIndex>> pair in used)
                {
                    count += pair.Value.Count;
                }


                mesh.Vertices = vertices;
                mesh.Indices = indices;
            }
        }

        
        private void Add(Dictionary<int, List<UsedIndex>> dictionary, int index0, int triIndex)
        {
            if (!dictionary.ContainsKey(index0))
            {
                List<UsedIndex> list = new List<UsedIndex>();
                UsedIndex ind = new UsedIndex(index0, triIndex);
                list.Add(ind);
                dictionary.Add(index0, list);
            }
            else
            {
                UsedIndex ind = new UsedIndex(index0, triIndex);
                dictionary[index0].Add(ind);
            }
        }

        public Vector3 CalculateNormal(Vector3 vertex1, Vector3 vertex2, Vector3 vertex3)
        {
            Vector3 vec1 = vertex2 - vertex1;
            Vector3 vec2 = vertex2 - vertex3;
            Vector3 normal = Vector3.Cross(vec1, vec2);
            if (normal != Vector3.Zero)
            {
                normal.Normalize();
            }

            return normal;
        }
        /// <summary>
        /// Returns the Next Multiple of the Value
        /// </summary>
        /// <param name="value"></param>
        /// <param name="multiple"></param>
        /// <returns></returns>
        public static int NextTriangle(int value)
        {
            int multiple = 3;
            //m = number
            //n = multiple
            //((m + n - 1) / n ) * n
            int val = Math.Abs(value);
            if (value == multiple ||
                value % multiple == 0)
                return value;                      

            int v = 0;
            if (value < 0)
            {
                v = ((val + multiple - 1) / multiple) * -multiple;
            }
            else
            {
                v = ((val + multiple - 1) / multiple) * multiple;
            }

            if (value < v)
            {
                v -= 3;
            }
            return v;
        }

        private void Save(Stream stream)
        {
            StreamWriter writer = new StreamWriter(stream);

            // Write Version
            writer.Write("BMv0.1");

            writer.WriteLine();
            writer.WriteLine("expNormals:" + Options.ExportNormals);
            writer.WriteLine("expTang:" + Options.ExportTangentSpaceInfo);
            writer.WriteLine("expSkin:" + Options.ExportSkinning);

            // Write Meshes
            writer.Write("m:" + Meshes.Count);
            for (int i = 0; i < Meshes.Count; i++)
            {
                MeshData mesh = Meshes[i];

                // Write Vertices
                writer.WriteLine();
                writer.Write("vertex:" + mesh.Vertices.Count);
                for (int x = 0; x < mesh.Vertices.Count; x++)
                {
                    Vertex vertex = mesh.Vertices[x];

                    // Write Positions and Tex Coords
                    Vector3 pos = vertex.Position;
                    Vector2 texCoords = vertex.TexCoord;
                    writer.WriteLine();
                    writer.Write("v:" + pos.X.ToString(CultureInfo.InvariantCulture)
                        // Position
                        + ":" + pos.Y.ToString(CultureInfo.InvariantCulture)
                        + ":" + pos.Z.ToString(CultureInfo.InvariantCulture)
                        // Texture Coordinate
                        + ":" + texCoords.X.ToString(CultureInfo.InvariantCulture)
                        + ":" + texCoords.Y.ToString(CultureInfo.InvariantCulture) +
                        // Normal
                        ":" + vertex.Normal.X.ToString(CultureInfo.InvariantCulture) +
                        ":" + vertex.Normal.Y.ToString(CultureInfo.InvariantCulture) +
                        ":" + vertex.Normal.Z.ToString(CultureInfo.InvariantCulture));
                }

                // Write Indices
                writer.WriteLine();
                writer.Write("indices:" + mesh.Indices.Count);
                for (int y = 0; y < mesh.Indices.Count - 2; y += 3)
                {
                    int ind1 = mesh.Indices[y];
                    int ind2 = mesh.Indices[y + 1];
                    int ind3 = mesh.Indices[y + 2];

                    writer.WriteLine();
                    writer.Write("i:" + ind1 + ":" + ind2 + ":" + ind3);
                }
            }

            writer.Flush();
            writer.Dispose();
        }
    }
}
